package base.tools.controller;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 测试代码生成模板处理类
 * 
 * @author HyouSeki
 * @version 1.0.0 2018-12-24 16:03:32 初始创建
 */
public class VelocityHandler {
    
    private static final String METHOD_POST = "post";
    
    private static final String METHOD_GET = "get";
    
    private static final String METHOD_DELETE = "delete";
    
    private static String basePath = null;
    
    private File vmDir;
    
    private Class<?> controllerClass;
    
    private VelocityContext context;
    
    public VelocityHandler(Class<?> controllerClass) {
        this.controllerClass = controllerClass;

        // 取模板文件
        //URI uri = VelocityHandler.class.getResource(".").toURI();
        //vmDir = new File(uri);

        vmDir = new File(VelocityHandler.basePath + "\\base\\tools\\controller");

        System.out.println("vmDir : " + vmDir.getAbsolutePath());

        context = new VelocityContext();
    }
    
    public void makeTestCodeForClass() {
        
        context.put("packageName", controllerClass.getPackage().getName());
        context.put("controllerName", controllerClass.getSimpleName());
        
        List<TestCaseInfo> testCaseInfos = makeMethodInfos();
        context.put("testCaseInfos", testCaseInfos);
        
        velocityMerge();
    }
    
    private List<TestCaseInfo> makeMethodInfos() {
        List<TestCaseInfo> infos = new ArrayList<>();
        
        Method[] methods = controllerClass.getMethods();
        
        RequestMapping reqMappingOnClass = controllerClass.getAnnotation(RequestMapping.class);
        
        String baseUri;
        if (reqMappingOnClass == null) {
            baseUri = null;
        } else {
            baseUri = getPath(reqMappingOnClass.path());
            if (baseUri == null) {
                baseUri = getPath(reqMappingOnClass.value());
            }
        }
        
        baseUri = StringUtils.trimToEmpty(baseUri);
        
        for (Method method : methods) {
            
            // 使用PostMapping注解声明的POST方法
            PostMapping postMapping = method.getAnnotation(PostMapping.class);
            if (postMapping != null) {
                String path = getAnnotationPath(postMapping.path(), postMapping.value());
                TestCaseInfo item = makeTestCaseInfo(path, method.getName(), METHOD_POST, baseUri);
                infos.add(item);
                continue;
            }
            
            // 使用GetMapping注解声明的GET方法
            GetMapping getMapping = method.getAnnotation(GetMapping.class);
            if (getMapping != null) {
                String path = getAnnotationPath(getMapping.path(), getMapping.value());
                TestCaseInfo item = makeTestCaseInfo(path, method.getName(), METHOD_GET, baseUri);
                infos.add(item);
                continue;
            }
            
            // 使用DeleteMapping注解声明的GET方法
            DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
            if (deleteMapping != null) {
                String path = getAnnotationPath(deleteMapping.path(), deleteMapping.value());
                TestCaseInfo item = makeTestCaseInfo(path, method.getName(), METHOD_DELETE, baseUri);
                infos.add(item);
                continue;
            }
            
            // 使用RequestMapping注解声明请求，需要method属性判断请求方法类型
            RequestMapping reqMapping = method.getAnnotation(RequestMapping.class);
            if (reqMapping != null) {
                
                RequestMethod[] reqMethods = reqMapping.method();
                String[] paths = reqMapping.path();
                String[] values = reqMapping.value();
                String methodName = method.getName();
                String path = getAnnotationPath(paths, values);
                
                if (reqMethods == null || reqMethods.length == 0 || hasMethod(reqMethods, RequestMethod.GET)) {
                    TestCaseInfo item = makeTestCaseInfo(path, methodName, METHOD_GET, baseUri);
                    infos.add(item);
                    continue;
                }
                
                if (hasMethod(reqMethods, RequestMethod.POST)) {
                    TestCaseInfo item = makeTestCaseInfo(path, methodName, METHOD_POST, baseUri);
                    infos.add(item);
                    continue;
                }
                
                if (hasMethod(reqMethods, RequestMethod.DELETE)) {
                    TestCaseInfo item = makeTestCaseInfo(path, methodName, METHOD_DELETE, baseUri);
                    infos.add(item);
                }
                
            }
        }
        
        return infos;
    }
    
    private String getPath(String[] paths) {
        if (paths == null || paths.length == 0) {
            return null;
        }
        
        return paths[0];
    }
    
    private String getMethodName(String src) {
        return src.substring(0, 1).toUpperCase() + src.substring(1);
    }
    
    private String getAnnotationPath(String[] paths, String[] values) {
        String path = getPath(paths);
        if (path == null) {
            path = getPath(values);
        }
        return path;
    }
    
    private TestCaseInfo makeTestCaseInfo(String path, String methodName, String methodType, String baseUri) {
        TestCaseInfo item = new TestCaseInfo();
        item.setMethod(methodType);
        item.setMethodName(getMethodName(methodName));
        item.setUri(baseUri + path);
        return item;
    }
    
    private boolean hasMethod(RequestMethod[] reqMethods, RequestMethod theMethod) {
        for (RequestMethod item : reqMethods) {
            if (item.equals(theMethod)) {
                return true;
            }
        }
        
        return false;
    }
    
    private void velocityMerge() {
        
        try (BufferedWriter writer = getWriter()) {
            
            /*
             * setup
             */
            Properties p = new Properties();
            p.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, vmDir.getAbsolutePath());
            p.setProperty(RuntimeConstants.ENCODING_DEFAULT, "UTF-8");
            p.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8");
            Velocity.init(p);
            
            /*
             * get the Template object. This is the parsed version of your
             * template input file. Note that getTemplate() can throw
             * ResourceNotFoundException : if it doesn't find the template
             * ParseErrorException : if there is something wrong with the VTL
             * Exception : if something else goes wrong (this is generally
             * indicative of as serious problem...)
             */
            Template template = null;
            
            try {
                template = Velocity.getTemplate("ControllerTestTemplate.vm", "UTF-8");
            } catch (ResourceNotFoundException rnfe) {
                rnfe.printStackTrace();
                return;
            } catch (ParseErrorException pee) {
                pee.printStackTrace();
                return;
            }
            
            if (template != null) {
                template.merge(context, writer);
            }
            
            /*
             * flush and cleanup
             */
            writer.flush();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private BufferedWriter getWriter() throws Exception {
        String filePath = getOutputFilePath();
        FileOutputStream fos = new FileOutputStream(filePath);
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        return new BufferedWriter(osw);
    }
    
    private String getOutputFilePath() {
        StringBuilder sb = new StringBuilder();
        sb.append(VelocityHandler.basePath);
        
        String pachageName = controllerClass.getPackage().getName();
        String[] dirs = pachageName.split("\\.");
        
        for (String dir : dirs) {
            sb.append("\\").append(dir);
        }
        
        String testClassDir = sb.toString();
        File testClassDirFile = new File(testClassDir);
        if (!testClassDirFile.exists()) {
            testClassDirFile.mkdirs();
        }
        
        sb.append("\\");
        sb.append(controllerClass.getSimpleName());
        sb.append("Test.java");
        return sb.toString();
    }
    
    public static void setBasePath(String basePath) {
        VelocityHandler.basePath = basePath;
    }
    
}
